package callForPapers;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import sistema.AreaDeInteres;
import sistema.Sistema;
import sistema.Usuario;
import AsignacionDeRevisores.AsignacionDeRevisores;
import AsignacionDeRevisores.AsignacionPorAlta;
import AsignacionDeRevisores.AsignacionPorAreasDeInteres;
import AsignacionDeRevisores.AsignacionPorMayoria;
import estadosCFP.EstadoCallForPaper;

public class CallForPaper {

	private Usuario autor;
	private String nombre;
	private Calendar fechaTopeDePresentacion;
	private Calendar fechaTopeDeRevision;
	private Sistema sistema;
	private Set<AreaDeInteres> areasDeInteres;
	private Set<Usuario> revisores;
	private Set<Paper> papers;
	private AsignacionDeRevisores asigandorDeRevisores;
	private EstadoCallForPaper estado;

	// //////////////////////////////////////////////////////////////////////////////////////////

	// getters and setters

	public String getNombre() {
		return this.nombre ;
	}

	public void setNombre(String nombre) {
		this.nombre = nombre;
	}

	protected EstadoCallForPaper getEstado() {
		return this.estado;
	}

	protected void setEstado(EstadoCallForPaper estado) {
		this.estado = estado;
	}

	protected AsignacionDeRevisores getAsigandorDeRevisores() {
		return this.asigandorDeRevisores;
	}

	protected void setSistema(Sistema sistema) {
		this.sistema = sistema;
	}

	protected void setRevisores(Set<Usuario> revisores) {
		this.revisores = revisores;
	}

	protected void setPapers(Set<Paper> papers) {
		this.papers = papers;
	}

	public Calendar getFechaTopeDePresentacion() {
		return this.fechaTopeDePresentacion;
	}

	public void setFechaTopeDePresentacion(Calendar fechaTopeDePresentacion) {
		this.fechaTopeDePresentacion = fechaTopeDePresentacion;
	}

	public Calendar getFechaTopeDeRevision() {
		return this.fechaTopeDeRevision;
	}

	public void setFechaTopeDeRevision(Calendar fechaTopeDeRevision) {
		this.fechaTopeDeRevision = fechaTopeDeRevision;
	}

	public Sistema getSistema() {
		return this.sistema;
	}

	public Set<AreaDeInteres> getAreasDeInteres() {
		return this.areasDeInteres;
	}

	public void setAreasDeInteres(Set<AreaDeInteres> areasDeInteres) {
		this.areasDeInteres = areasDeInteres;
	}

	public Set<Usuario> getRevisores() {
		// el geter de los revisores del cfp
		return this.revisores;
	}

	public Set<Paper> getPapers() {
		// el geter de los papers del cfp
		return this.papers;
	}

	public AsignacionDeRevisores getAsignadorRevisores() {
		return this.asigandorDeRevisores;

	}

	public void setAsigandorDeRevisores(AsignacionDeRevisores asignacion) {
		this.asigandorDeRevisores = asignacion;

	}

	public Usuario getAutor() {
		return this.autor;
	}

	public void setAutor(Usuario autor) {
		this.autor = autor;
	}

	// ////////////////////////////////////////////////////////////////////////////////////////////////

	// constructores

	public CallForPaper(Usuario autor, String nombre,
			Calendar fechaTopeDePresentacion, Calendar fechaTopeRevision,
			Set<AreaDeInteres> areasDeInteres, Sistema sistema) {

		// constructor que recibe una lista de areas de interes
		this.autor = autor;
		this.nombre = nombre;
		this.fechaTopeDePresentacion = fechaTopeDePresentacion;
		this.fechaTopeDeRevision = fechaTopeRevision;
		this.sistema = sistema;
		this.areasDeInteres = areasDeInteres;
		this.revisores = new HashSet<Usuario>();
		this.papers = new HashSet<Paper>();
		this.asigandorDeRevisores = new AsignacionPorAlta();
		this.estado = EstadoCallForPaper.INICIAL;
	}

	public CallForPaper(Usuario autor, String nombre,
			Calendar fechaTopeDePresentacion, Calendar fechaTopeRevision,
			AreaDeInteres areaInteres, Sistema sistema) {

		// constructor que recibe solo una area de interes.

		this.autor = autor;
		this.nombre = nombre;
		this.fechaTopeDePresentacion = fechaTopeDePresentacion;
		this.fechaTopeDeRevision = fechaTopeRevision;
		this.sistema = sistema;
		this.areasDeInteres = new HashSet<AreaDeInteres>();
		this.areasDeInteres.add(areaInteres);
		this.revisores = new HashSet<Usuario>();
		this.papers = new HashSet<Paper>();
		this.asigandorDeRevisores = new AsignacionPorAlta();
		this.estado = EstadoCallForPaper.INICIAL;

	}

	// ///////////////////////////////////////////////////////////////////////////////////////////////

	//metodos
	
	/**
	 * true si soy el creador del cfp
	 */
	public boolean soyDueno(Usuario usuario) {
		return this.getAutor().equals(usuario);
		
	}
	
	/**
	 * agrega un area al cfp.......... no acepta duplicados ya que es un SET
	 *
	 */
	public void agregarAreaDeInteres(AreaDeInteres area) {

		this.areasDeInteres.add(area);

	}

	/**
	 * Delega en el estado del paper la posibilidad de agregar un paper (siempre
	 * que coincida con al menos una area del cfp y no sea revisor del mismo cfp
	 */
	public void agregarPaper(Paper paper) {

		this.estado.agregarPaper(paper, this);
	}

	/**
	 * true si el paper pasado por parametro tiene en comun
	 *	al menos un area con el cfp
	 * @param paper
	 * @return
	 */
	public boolean tieneAreaEnComun(Paper paper) {

	return paper.tieneAreaEnComun(this);
	}

	/**
	 * lista de users que tiene al menos 1 area de interes en comun con el cfp
	 * @return
	 */
	public List<Usuario> posiblesRevisores() {

	List<Usuario> posiblesRevisores = new ArrayList<Usuario>();

		for (Usuario usuarioSistema : this.getSistema().getUsuarios()) {
			if (this.tieneAreaEnComun(usuarioSistema)) {
				posiblesRevisores.add(usuarioSistema);
			}
		}

		return posiblesRevisores;

	}
	
	/**
	 * un metodo viejo que quedo de mi interpretacion de que un creador de un cfp no podia ser revisor del mismo
	 */
/*
	private List<Usuario> getUsuariosSinAutor() {

		// retorna todos los usuarios del sistema sin el autor del cfp

		List<Usuario> listaUsuarios = this.getSistema().getUsuarios();

		if (listaUsuarios.contains(this.autor)) {
			listaUsuarios.remove(this.autor);
		}

		return listaUsuarios;

	}
*/
	
	/**
	 * // true si el usuario coincide con 1 area del cfp
	 * @param usuarioSistema
	 * @return
	 */
	public boolean tieneAreaEnComun(Usuario usuarioSistema) {

			if(usuarioSistema.tieneAreaEnComun(this)){
					return true;
				}
				
		return false;
	}

	/**
	 * 	agrega un revisor al cfp solo si no es autor del mismo
	 *	habria que chequear que un revisor no se agrege si es que ya tiene
	 *	papers presentados, pero como este metodo solo se usa al crear el
	 * 	cfp no tendria que haber problemas
	 * 
	 * @param usuario
	 * @throws Exception 
	 */
	public void agregarRevisor(Usuario usuario) {
	
		if (usuario != this.autor)
			this.revisores.add(usuario);
		else {
			try {
				throw new Exception("Sos autor de un paper en el cfp");
			} catch (Exception e) {
				e.getMessage();
			}
		}
		
	}
	
/**
 * lista de los revisores del cfp que tiene al menos un area de interes
 *	requerida
 * @param areaRequerida
 * @return
 */
	public List<Usuario> usuariosPorInteres(AreaDeInteres areaRequerida) {

	List<Usuario> listaRevisores = new ArrayList<Usuario>();

		for (Usuario revisorActual : this.getRevisores()) {
			if (revisorActual.tieneAreaEnComun(this)) {
				listaRevisores.add(revisorActual);
			}
		}

		return listaRevisores;
	}

	/**
	 * true si el revisor pasado por parametro, tiene el area de la pasada por
	 * parametro
	 * 
	 * @param revisorActual
	 * @param areaRequerida
	 * @return
	 */
	/*
	private boolean tieneAreaEnComun(Usuario revisorActual,
			AreaDeInteres areaRequerida) {

			for (AreaDeInteres areaRevisor : revisorActual.getAreasDeInteres()) {
			if (areaRevisor.equals(areaRequerida)) {
				return true;
			}
		}
		return false;
	}
*/
	
	/**
	 * devuelve una lista con los usuarios del sistema donde se
	 * acomodan como primeros los que tienen mayor cantidad de similitudes al
	 * area del cfp
	 * 
	 * @return
	 */
	public List<Usuario> ordenarRevisoresPorAfinidad() {

		List<Usuario> listaOrdenadaRevisores = new ArrayList<Usuario>();
		Map<Usuario, Integer> revisoresMap = new HashMap<Usuario, Integer>();
		Map<Integer, List<Usuario>> revisoresMapInvertido;

		for (Usuario revisor : this.getSistema().getUsuarios()) {
			// map con <usuario,integer> donde cada revisor tiene un puntaje de
			// las cantidad
			// de areas que tiene en comun con el cfp
			revisoresMap.put(revisor, this.numeroAreasEnComun(revisor));
		}

		revisoresMapInvertido = this.invertirMap(revisoresMap); // invierte el
																// map de
																// <user,int> a
																// <int,users>
																// y me queda
																// <0;userA,userB>
																// , <1, userC>
																// ,
																// <etc, users>

		listaOrdenadaRevisores = this
				.convertirMapToArray(revisoresMapInvertido);

		return listaOrdenadaRevisores;
	}

	/**
	 * convierte la lista de revisoresMapInvetido en un array con los primeros
	 * los que tengan la key mas grande y los que tienen key = 0 no los agrega y
	 * si la lista que recibe esta vacia regresa un array vacio
	 * 
	 * @param revisoresMapInvertido
	 * @return
	 */
	private List<Usuario> convertirMapToArray(
			Map<Integer, List<Usuario>> revisoresMapInvertido) {

		

		Set<Integer> setEntry = revisoresMapInvertido.keySet();
		List<Usuario> usuariosOrdenados = new ArrayList<Usuario>();

		int minimo = this.minimo(setEntry); // la key mas chica

		if (minimo == 0) { // quita los usuarios que estan en la categoria 0 y
							// se queda con todos los demas, SI ES que hay
							// usuarios
							// que esten en la categoria 0
			minimo++;
		}
		int maximo = this.maximo(setEntry); // la key mas alta
											// si revisoresMapInvertido es vacia
											// devuelve 0

		while (maximo > minimo) { // si el maximo = 0 ( lista recibida es vacia
									// ) entonces
									// minimo = 1 y maximo = 0 y no entra

			for (Usuario usuario : revisoresMapInvertido.get(maximo)) {
				usuariosOrdenados.add(usuario);
			}
			maximo--;

		}

		if (maximo != 0) { // para que no agrege un usuario(null) si es que
							// revisoresMapInvertido en vacio ??
			for (Usuario usuario : revisoresMapInvertido.get(maximo)) {
				usuariosOrdenados.add(usuario);

			}
		}

		return usuariosOrdenados;
	}

	private int minimo(Set<Integer> setEntry) {
		int minimoEncontrado = this.maximo(setEntry);

		for (int actual : setEntry) {
			if (actual < minimoEncontrado) {
				minimoEncontrado = actual;
			}
		}
		return minimoEncontrado;
	}

	private int maximo(Set<Integer> setEntry) {
		int maximoEncontrado = 0;

		for (int actual : setEntry) {
			if (actual > maximoEncontrado) {
				maximoEncontrado = actual;
			}
		}
		return maximoEncontrado;
	}

	/**
	 * invierte el map de <user,int> y retorna <int , list<user> > si no hay
	 * revisores retorna un map vacio porque nunca entra en el while
	 * 
	 * @param revisoresMap
	 * @return
	 */
	private Map<Integer, List<Usuario>> invertirMap(
			Map<Usuario, Integer> revisoresMap) {

		 

		Map<Integer, List<Usuario>> mapInvertido = new HashMap<Integer, List<Usuario>>();
		Set<Entry<Usuario, Integer>> revisoresSet = revisoresMap.entrySet();
		Iterator<Entry<Usuario, Integer>> iteratorUsersInt = revisoresSet
				.iterator();

		while (iteratorUsersInt.hasNext()) {

			Entry<Usuario, Integer> par = iteratorUsersInt.next();
			// por cada key(usuario) del map recibido saco el user y su puntaje
			Usuario usuario = par.getKey();
			int puntaje = par.getValue();

			if (mapInvertido.get(puntaje) == null) { // si no hay un puntaje que
														// lo cree y agrega al
														// user a su lista
				mapInvertido.put(puntaje, new ArrayList<Usuario>());
				mapInvertido.get(puntaje).add(usuario);
			} else {
				mapInvertido.get(puntaje).add(usuario); // si el puntaje existe
														// solo agrega al
														// usuario a la lista
			}

		}

		return mapInvertido;
	}

	/**
	 * retorna un int representando la cantidad de areas que un usuario tiene en
	 * comun con el cfp
	 * 
	 * @param revisor
	 * @return
	 */
	private int numeroAreasEnComun(Usuario revisor) {
		

		int ocurrencias = 0;

		for (AreaDeInteres areaCfp : this.getAreasDeInteres()) {
			if (areaCfp.isIncluded(revisor.getAreasDeInteres())) {
				ocurrencias++;
				}	
			}
		
		
		return ocurrencias;
	}

	public boolean soyRevisor(Usuario revisor) {

		return this.getRevisores().contains(revisor);
	}

	public void asignadorPorAlta() {
		this.setAsigandorDeRevisores(new AsignacionPorAlta());
	}

	public void asignadorPorAreasDeInteres() {
		this.setAsigandorDeRevisores(new AsignacionPorAreasDeInteres());
	}

	public void asignadorPorMayoria() {
		this.setAsigandorDeRevisores(new AsignacionPorMayoria());
	}

	public void asignarPapersARevisores() {
		this.getAsignadorRevisores().asignarRevisores(this);
	}

	public void calcularNotaPapers() {

		for (Paper paper : this.getPapers()) {
			paper.calcularNotaFinal();
		}
	}

	public List<Paper> verTrabajosEnviados(Usuario autor) {

		List<Paper> papers = new ArrayList<Paper>();

		for (Paper paper : this.getPapers()) {
			if (paper.esAutor(autor)) {
				papers.add(paper);
			}
		}
		return papers;

	}

	public Set<Paper> verTrabajosARevisar(Usuario revisor) {

		return revisor.getPapersARevisar();

	}

	/**
	 * Este metodo se debe invocar luego de que se le terminen de cargar todos
	 * los revisores a un cfp, y sus areas de interes y toooodo lo que le haga
	 * falta a un cfp para que el mismo cambie el estado a ABIERTO y comience a
	 * recibir Papers
	 */

	public void abrirCfp() {
		this.estado = EstadoCallForPaper.ABIERTO;
	}

	/**
	 * este metodo tiene que: asignar los revisores, mandar los mails a los
	 * revisores y setear el estadoCFP CERRADO")
	 */

	public void cerrarCfp() {
		this.asigandorDeRevisores.asignarRevisores(this);
		this.enviarMailsRevisores();
		this.estado = EstadoCallForPaper.CERRADO;

	}

	private void enviarMailsRevisores() {
		for (Paper paperActual : this.getPapers()) {
			paperActual.mandarMailRevisores();
		}
	}

	/**
	 * "este metodo tiene que: calcular las notas, mandar los mails a los autores y setear el estadoCFP FINALIZADO"
	 * )
	 */

	public void finalizarCfp() {
		this.calcularNotasPapers();
		this.enviarMailAutores();
		this.estado = EstadoCallForPaper.FINALIZADO;
	}

	public void calcularNotasPapers() {
		for (Paper paperActual : this.getPapers()) {
			paperActual.calcularNotaFinal();
		}
	}

	private void enviarMailAutores() {
		for (Paper paperActual : this.getPapers()) {
			paperActual.mandarMailAutor();
		}
	}

	public void hoyEs(Calendar hoy) {
		this.estado.hoyEs(hoy, this);
	}
}